/////////////////////////////////////////////////////////////////////////
// $Id: tcache.h 10613 2011-08-21 16:44:02Z xint $
/////////////////////////////////////////////////////////////////////////
//
//   Copyright (c) 2007-2011 Stanislav Shwartsman
//          Written by Stanislav Shwartsman [sshwarts at sourceforge net]
//
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA B 02110-1301 USA
//
/////////////////////////////////////////////////////////////////////////

#include "bochs.h"
#include "cpu.h"
#include "stats.h"
#include "compiler.h"
#include "param_names.h"

#include <stddef.h> // for offsetof

#define LOG_THIS BX_COMPILER_THIS_PTR

// used to keep track of all the compilation threads alive and
// shutdown them when necessary.
static Bit32u           compiler_thread_count = 0;
static pthread_cond_t   compiler_count_cond   = PTHREAD_COND_INITIALIZER;
static pthread_mutex_t  compiler_count_mutex  = PTHREAD_MUTEX_INITIALIZER;

Bit32u find_field_offset_by_name(const char* fieldname)
{
  return bx_pc_system.find_field_offset_by_name(fieldname);
}

Bit32u find_reg_offset_by_index(Bit32u index, Bit32u type)
{
  return bx_pc_system.find_reg_offset_by_index(index, type);
}

void* pthread_member_func_thunk(void* comp_unit)
{
  (static_cast<BX_COMP_C*>(comp_unit)->compiler_run)();
}

void BX_COMP_C::initialize_prefix(void) 
{
  // initialize the log prefix.
  Bit8u pname[16];
  Bit8u logname[16];
  sprintf((char*)pname, "JIT");
  sprintf((char*)logname, "JIT");
  put((char*)logname, (char*)pname);
}

void BX_COMP_C::initialize_jit_func(void)
{
  // initialize the JIT initialize function.
  *(void **)(&j_callbacks.init_func) = (void*)dlsym(lib_handle, BX_COMP_INIT_FUNC);

  // initialize the JIT other callbacks.
  *(void **)(&j_callbacks.comp_func) = (void*)dlsym(lib_handle, BX_COMP_COMP_FUNC);
  // initialize the JIT other callbacks.
  *(void **)(&j_callbacks.jitc_func) = (void*)dlsym(lib_handle, BX_COMP_JITC_FUNC);
  // initialize the JIT other callbacks.
  *(void **)(&j_callbacks.intc_func) = (void*)dlsym(lib_handle, BX_COMP_INTC_FUNC);
  // initialize the JIT other callbacks.
  *(void **)(&j_callbacks.invl_func) = (void*)dlsym(lib_handle, BX_COMP_INVL_FUNC);

  // everything is initialized ?
  if (1)
  {
    BX_ASSERT((j_callbacks.init_func!=NULL));
    BX_ASSERT((j_callbacks.comp_func!=NULL));
    BX_ASSERT((j_callbacks.jitc_func!=NULL));
    BX_ASSERT((j_callbacks.intc_func!=NULL));
    BX_ASSERT((j_callbacks.invl_func!=NULL));
  }
}

void BX_COMP_C::initialize_int_func(void)
{
  // initialize the interpreter callback functions.
  i_callbacks.find_field_offset_by_name = (void*) find_field_offset_by_name;
  i_callbacks.find_reg_offset_by_index = (void*) find_reg_offset_by_index;
  i_callbacks.interpret = (void*) BochsCall;
  i_callbacks.naked_interpret = (void*) NakedBochsCall;
  i_callbacks.resolve_modrm = (void*) GetResolveModRM;
  i_callbacks.modrm_xlation = (void*) GetModRMXlation;
}

bx_bool BX_COMP_C::initialize_library(void) 
{
  // compilation thread initialization can only be called once.
  BX_ASSERT(init_status == COMP_INIT_UNCALLED);

  lib_handle = dlopen((const char*) lib_path, RTLD_LAZY);
    
  if (!lib_handle) 
  {
    BX_PANIC(("Failed to load JIT shared library: %s\n", dlerror()));
    return COMP_INIT_FAILED;
  }

  // initialize the JIT compiler callbacks.
  initialize_jit_func();
  // initialize the INTERPRETER callbacks.
  initialize_int_func();

  return COMP_INIT_SUCCESSFUL;
}

bx_bool BX_COMP_C::initialize_thread(void) 
{
  // compilation thread initialization can only be called once.
  BX_ASSERT(init_status == COMP_INIT_UNCALLED);
  Bit32u rc = pthread_create(&thread_id, NULL, 
                             pthread_member_func_thunk, 
                             this);
  if (rc) return COMP_INIT_FAILED;

  return COMP_INIT_SUCCESSFUL;
}

void BX_COMP_C::initialize(Bit8u* libpath)
{
  // initialize the log prefix.  
  initialize_prefix();

  // initialize the compilation library.
  if (libpath == NULL) 
  {
    // jit is disabled.
    BX_INFO(("Emulator JIT is disabled, running in interpreter only mode"));
    init_status = COMP_INIT_UNNEEDED;
    return; 
  } 
  else 
  {
    lib_path = libpath;
  }

  // initialize the compilation library.
  if (initialize_library() == COMP_INIT_FAILED) 
  {
    BX_PANIC(("failed to initialize JIT shared library"));
  }

  // initialize the compilation thread.
  if (initialize_thread() == COMP_INIT_FAILED) 
  {
    BX_PANIC(("failed to initialize JIT compilation thread"));
  }

  // finally, call the initialization function in the JIT.
  ((void(*)(void))j_callbacks.init_func)();
  ((void(*)(void*))j_callbacks.jitc_func)(&jit_code);
  ((void(*)(void*))j_callbacks.intc_func)(&i_callbacks);
  
  init_status = COMP_INIT_SUCCESSFUL;
  BX_INFO(("Emulator JIT thread initialization successful"));

  // update the compilation thread count so that it can be accounted
  // for when a termination is called.
  pthread_mutex_lock(&compiler_count_mutex);
  compiler_thread_count++;
  pthread_mutex_unlock(&compiler_count_mutex);
}

void BX_COMP_C::deinitialize(void) 
{
  // tell the compilation thread because we are exiting ...
  run_status = COMP_STOP; 
  pthread_cond_signal(&queue_cond);

  // now wait for the compilation thread to exit. at this point, the
  // compilation thread might have exited already. compiler_thread_count will be
  // 0 in that case and pthread_cond_wait will not be called.
  pthread_mutex_lock(&compiler_count_mutex);
  while (compiler_thread_count != 0) 
  {
    BX_INFO(("waiting for JIT thread to finish"));
    pthread_cond_wait(&compiler_count_cond, &compiler_count_mutex);
  }
  pthread_mutex_unlock(&compiler_count_mutex);

  // the compilation thread has exited. it is now safe to unload 
  // the compilation library.
  dlclose(lib_handle);

  BX_INFO(("Emulator JIT thread deinitialization successful"));
}

void BX_COMP_C::compile(bxICacheEntry_c *entry) 
{
  // hold the read lock on the entry so that it can not be SMC'ed
  // while native code is generated for it.
  entry->acquire_entry_rdlock();
  bxInstruction_c *begin = &entry->i[0];
  bxInstruction_c *end   = begin + (entry->tlen);
  
  // compile it !!!.
  void *nAddr = ((void*(*)(void*,void*))j_callbacks.comp_func)(begin, end);
  stats->comp_bcount ++;

  if (entry->tlen==0) 
  {
    // the entry has been invalidated from the time it is
    // submitted and the time it is compiled.
    entry->raddress = 0;
  }
  else 
  {
    // the entry is valid. assign the compiled address to it.
    entry->raddress = (bx_address)nAddr;
  }

  entry->release_entry_rwlock();
}

void BX_COMP_C::compiler_run(void) 
{
  pthread_mutex_lock(&queue_mutex); 
  // wait for the request to come in.
  for(;;) 
  {
    // are we stopping ?
    if (run_status == COMP_STOP) 
    {
      // stop the thread now.
      pthread_mutex_lock(&compiler_count_mutex);
      compiler_thread_count--;
      pthread_cond_signal(&compiler_count_cond);
      pthread_mutex_unlock(&compiler_count_mutex);
      return;
    } 

    // wait for the compilation requests.
    pthread_cond_wait(&queue_cond, &queue_mutex); 

    while(pending_entries.size()) 
    {
      work_status |= COMP_COMPILING;
      // dequeue a compilation request.
      current = pending_entries.front();
      pending_entries.erase(pending_entries.begin());

      // submit compilation request while this one is being served.
      pthread_mutex_unlock(&queue_mutex);

       // compile the unit.
       compile(current);
       current = 0;

       // done compiling.
       work_status &= ~COMP_COMPILING;

       pthread_mutex_lock(&queue_mutex);

       // gained exclusive access to the pending_entries queue.
       // are we stopping ?
       if (run_status == COMP_STOP) 
       {
         // gave up exclusive access to the pending_entries queue.
         pthread_mutex_unlock(&queue_mutex);
         break;
       }
    }
    // no more compilation request. go to wait on condition.
  }
  pthread_mutex_unlock(&queue_mutex); 
}

void BX_COMP_C::compiler_submit(bxICacheEntry_c *request)
{
  if (init_status == COMP_INIT_SUCCESSFUL) 
  {
     // enqueue the compilation request and signal the compilation
     // thread to wake up.
     pthread_mutex_lock(&queue_mutex); 
     pending_entries.push_back(request);
     pthread_cond_signal(&queue_cond);
     pthread_mutex_unlock(&queue_mutex); 
  } 
  else 
  {
     BX_PANIC(("trying to submit compilation request to uninitialized JIT"));
  }
}

/// @ flushICacheEntries - flush all the compilation requests in the compilation
//  @ queue. it also waits the current compilation to finish.
//  @
//  @ parameters - none.
//  @
//  @ side effects - all compilation requests flushed.
//  @
//  @ return - none.
void BX_COMP_C::flushICacheEntries(void)
{
}

/// @ flushICacheEntry - flush the given request from the compilation queue.
//  @ it also waits for the current compilation to finish if it happens to be
//  @ the request to be flushed.
//  @
//  @ parameters.
//  @ request - the request to flush.
//  @
//  @ side effects - the given request flushed from the compilation queue.
//  @
//  @ return - none.
void BX_COMP_C::flushICacheEntry(bxICacheEntry_c *request)
{
}
